/**
 * 
 */
package com.upstairstechnology.oi.notification.email;

import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.support.PropertiesLoaderUtils;

import com.upstairstechnology.oi.determination.OutstandingIntegration;
import com.upstairstechnology.oi.notification.email.resolver.EmailResolver;

/**
 * Base implementation of email notification strategy. Contains logic for setup
 * of and sending emails. Creation of message body delegated to subclasses.
 * 
 * @author Jeff Jensen
 */
public abstract class AbstractNotificationStrategyEmail implements
        NotificationStrategyEmail {
    /** Logger instance. */
    private static final Logger LOGGER =
            LoggerFactory.getLogger(AbstractNotificationStrategyEmail.class);

    /** Configuration for sending emails. */
    private NotificationConfigurationEmail notificationConfigurationEmail;

    /** Strategy to convert SCM user ids to email addresses. */
    private EmailResolver emailResolver;

    /**
     * {@inheritDoc}
     */
    public void notify(Set<OutstandingIntegration> oiSet) throws IOException,
            MessagingException {
        Session session = prepareMailSession();
        MimeMessage msg = prepareMailMessage(session);
        Transport transport = prepareMailTransport(session);

        processOustandingIntegrations(oiSet, msg, transport);

        transport.close();
    }

    /**
     * Loops over the oiSet, updating the MIME message and delegating content
     * creation to {@link #processOutstandingIntegration}, and sending the
     * message.
     * 
     * @param oiSet
     *            Set of outstanding integrations to process.
     * @param msg
     *            The prepped MIME message.
     * @param transport
     *            The prepped transport for sending the messages.
     * @throws MessagingException
     *             When problems occur with setting the To address or the
     *             message content.
     */
    protected void processOustandingIntegrations(
            Set<OutstandingIntegration> oiSet, MimeMessage msg,
            Transport transport) throws MessagingException {
        Address[] toAddress = new Address[1];
        Iterator<OutstandingIntegration> i = oiSet.iterator();
        while (i.hasNext()) {
            OutstandingIntegration oi = i.next();

            String userId = oi.getUserId();
            toAddress[0] = emailResolver.getEmailAddress(userId);
            msg.setRecipients(RecipientType.TO, toAddress);

            String msgBody = processOutstandingIntegrationsForUser(i, oi);
            msg.setContent(msgBody, getContentType());
            msg.saveChanges();

            transport.sendMessage(msg, msg.getAllRecipients());
        }
    }

    /**
     * Get the content type for the message, e.g. "text/html".
     * 
     * @return The content type for the message.
     */
    protected abstract String getContentType();

    /**
     * Process all outstanding integrations for the same user. Implementing
     * methods must ensure to processing for only the user as specified in the
     * oi argument.
     * 
     * @param i
     *            Iterator for processing all outstanding integrations.
     * @param oi
     *            Current outstanding integration to process.
     * @return Email message body for the user.
     */
    protected abstract String processOutstandingIntegrationsForUser(
            Iterator<OutstandingIntegration> i, OutstandingIntegration oi);

    /**
     * Create and prepare a mail session using the configured properties.
     * 
     * @return Ready to use mail session.
     * @throws IOException
     *             When cannot load configured mail properties resource name.
     */
    protected Session prepareMailSession() throws IOException {
        String mailPropertiesResourceName =
                notificationConfigurationEmail.getMailPropertiesResourceName();
        Properties mailProperties =
                PropertiesLoaderUtils
                        .loadAllProperties(mailPropertiesResourceName);
        // TODO load System.getProperties() too, overriding ones in file
        // Properties systemProperties = System.getProperties();

        LOGGER.debug("mailPropertiesResourceName={}",
                mailPropertiesResourceName);
        LOGGER.debug("mailProperties={}", mailProperties);

        Session session = Session.getDefaultInstance(mailProperties);
        return session;
    }

    /**
     * Create and prepare the mail message - CC, subject.
     * 
     * Notes:
     * <ul>
     * <li>The email "from" address specified in mail.properties, so not set
     * here.</li>
     * <li>The email "to" address obtained from the {@link #emailResolver} for
     * the change author's user id of the outstanding integration.</li>
     * </ul>
     * 
     * @param session
     *            Mail session used to create and prepare the message.
     * @return Ready to use mail message.
     * @throws MessagingException
     *             When errors occur with preparing the message.
     */
    protected MimeMessage prepareMailMessage(Session session)
            throws MessagingException {
        MimeMessage msg = new MimeMessage(session);

        Address[] ccAddresses = notificationConfigurationEmail.getCcAddresses();
        String subject = notificationConfigurationEmail.getSubject();

        msg.setRecipients(RecipientType.CC, ccAddresses);
        msg.setSubject(subject);
        return msg;
    }

    /**
     * Prepare the mail transport. Create one transport and connect once for
     * program performance and efficiency on environment.
     * 
     * NOTE: Transport protocol specified by user in the configured
     * mail.properties file. Typically "smtp".
     * 
     * @param session
     *            The prepared mail session.
     * @return The prepared transport.
     * @throws NoSuchProviderException
     *             When no transport exists for the configured transport.
     * @throws MessagingException
     *             When problem occurs with transport connect.
     */
    protected Transport prepareMailTransport(Session session)
            throws NoSuchProviderException, MessagingException {
        Transport transport = session.getTransport();
        transport.connect();
        return transport;
    }

    /**
     * {@inheritDoc}
     */
    public NotificationConfigurationEmail getEmailConfiguration() {
        return notificationConfigurationEmail;
    }

    /**
     * {@inheritDoc}
     */
    @Required
    public void setEmailConfiguration(NotificationConfigurationEmail cfg) {
        this.notificationConfigurationEmail = cfg;
    }

    /**
     * Get the emailResolver.
     * 
     * @see {@link emailResolver}.
     * 
     * @return The emailResolver.
     */
    public EmailResolver getEmailResolver() {
        return emailResolver;
    }

    /**
     * Set the emailResolver.
     * 
     * @see {@link emailResolver}.
     * 
     * @param resolver
     *            The emailResolver to set.
     */
    @Required
    public void setEmailResolver(EmailResolver resolver) {
        this.emailResolver = resolver;
    }
}
